﻿//Copyright (C) <2013>  <jonathan cleeve norton> All Rights Reserved 
//Contact jon.norton@fin-plus.co.uk website <http://www.fin-plus.co.uk/>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
using FinPlusCommon;
using log = Logger.Logger;
using u = FinPlusUtility.Utility;

namespace FinPlusComponents
{
    public class FinPlusValuation: FinPlusComponent
    {
        public override string Id { get; set; }
        public override string[] States { get { return new string[] { }; } }
        public override string[] TestStates { get { return new string[] { "ModelTest", "IsValid" }; } }
        public override IFinPlusFunctions Model { get { return _accum.Model; } }
        public override string ModelTest { get { return Model.ToString(); } }
        public override bool IsValid { get; set; }

        private enum DockControls { FinPlusMarket, FinPlusDataConn, FinPlusChildService, FinPlusCache, FinPlusAccumulator, QLAnalytics, Default }
        private enum Params { LinkedCurve, MarketUpdate, CurveName, Instrument, End, Shock, Refresh, LevelUpdate, FailedTrade }
        private enum ValType { Updates, Live, UpdatesWithShocks, LiveWithShocks }
        private enum Connection { Instruments, Cache, Report, Exception }

        private IDictionary<int, String[]> _subTotalKeys;
        private readonly IFinPlusQueueThrottle _throttle;
        private IFinPlusComponent _marketConn, _cacheConn, _reportConn;
        private FinPlusAccumulator _accum;
        private QLAnalytics _analytics;
        private readonly ValType _valType;
        private readonly IList<string> _subTotals;
        private bool _hasInitialised;

        //construct
        public FinPlusValuation(string valType, string subTotals, string throttle)
        {
            _valType = u.EnumTryParse(valType, ValType.LiveWithShocks);
            _subTotals = u.Split(subTotals, ',').ToList();
            _throttle = new FinPlusQueueThrottle(double.Parse(throttle), true);
        }

        //common control interface
        public override void Dock(object component, string dock) 
        {
            try
            {
                switch (u.EnumTryParse<DockControls>(component.GetType().Name, DockControls.Default))
                {
                    case DockControls.FinPlusCache:
                    case DockControls.FinPlusMarket:
                    case DockControls.FinPlusDataConn:
                    case DockControls.FinPlusChildService:
                    case DockControls.Default: AddConnection((IFinPlusComponent)component, dock); break;
                    case DockControls.FinPlusAccumulator: _accum = (FinPlusAccumulator)component; break;
                    case DockControls.QLAnalytics: _analytics = (QLAnalytics)component; break;
                }
            }
            catch (Exception e)
            {
                 IsValid = log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        public override void Initialise()
        {
            if (_hasInitialised) return;

            _subTotalKeys = _cacheConn.Model.Values.ToDictionary(f => f.Id, f => f.Where(i => _subTotals.Contains(i.Name)).Select(i => i.Value).ToArray());
            _throttle.Start(Update);
            _cacheConn.Model.FuncUpdate += Trade_Updated;

            if (_valType == ValType.LiveWithShocks || _valType == ValType.Live)
                _marketConn.Model.FuncUpdate += Market_Updated;

            _marketConn.Model.Set(Params.Refresh.ToString(), Params.LevelUpdate.ToString(), string.Empty);

            Value();

            IsValid = _hasInitialised = true;
        }

        public override void CommandRouter(IFinPlusFunction func) 
        {
            try
            {
                CaptureCommand(func);

                switch ((Commands)Enum.Parse(typeof(Commands), func.Name, true))
                {
                    case Commands.TradeUpdate: ThrottleTrade((IFinPlusFunction)func.Value(Commands.TradeUpdate.ToString())); break;
                    case Commands.MarketUpdate: ThrottleMarket(func); break;
                    default: throw new Exception(string.Format("event not recognised {0}", func.Name));
                }
            }
            catch (Exception e)
            {
               IsValid = log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        public override void Dispose()
        {
            if (_marketConn != null)
                _marketConn.Dispose();
            if (_cacheConn != null)
                _cacheConn.Dispose();
            if (_throttle != null)
                _throttle.Dispose();
        }

        //private
        private void AddConnection(IFinPlusComponent conn, string dock)
        {
            try
            {
                switch (u.EnumTryParse<Connection>(dock, Connection.Exception))
                {
                    case Connection.Instruments: _marketConn = conn; break;
                    case Connection.Cache: _cacheConn = conn; break;
                    case Connection.Report: _reportConn = conn; break;
                    case Connection.Exception: throw new NotImplementedException();
                }
            }
            catch (Exception e)
            {
                log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        private void ThrottleMarket(IFinPlusFunction marketUpdate)
        {
            _throttle.Put(marketUpdate.Name, marketUpdate);
        }

        private void ThrottleTrade(IFinPlusFunction trade)
        {
            _throttle.Put(trade.Id.ToString(), trade);
        }

        private void Update(IDictionary<string, object> funcs)
        {
            if (_valType == ValType.LiveWithShocks || _valType == ValType.Live || funcs.ContainsKey(Commands.MarketUpdate.ToString()))
                Value();
            else
                Value(funcs.ToDictionary(f => f.Key, f => (IFinPlusFunction)f.Value));
        }

        private void Value(IDictionary<string, IFinPlusFunction> trades = null)
        {
            try
            {
                Value("NPV|Base", true, trades);
                if (_valType == ValType.LiveWithShocks || _valType == ValType.UpdatesWithShocks) 
                    ValueShocks(trades);

                _accum.Refresh();
                _throttle.IsBusy = false;
            }
            catch (Exception e)
            {
                IsValid = log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        private void ValueShocks(IDictionary<string, IFinPlusFunction> trades)
        {
            try
            {
                var instruments = _analytics.Model.Values.OrderBy(f => (DateTime)f.Value(Params.End.ToString(), DateTime.MinValue)).ToList();
                if ((_valType == ValType.LiveWithShocks || _valType == ValType.UpdatesWithShocks) && _analytics.Shocks != null)
                    foreach (var shock in _analytics.Shocks)
                        ValueShock(instruments, shock, trades);
            }
            catch (Exception e)
            {
                IsValid = log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        private void ValueShock(IList<IFinPlusFunction> instruments, KeyValuePair<string, int> shock, IDictionary<string, IFinPlusFunction> trades)
        {
            IList<string> curveNames = null;
            if(trades != null) curveNames = trades.Select(f => (string)(f.Value[Params.CurveName.ToString()] ?? string.Empty)).Distinct().ToList();

            foreach (var instrument in instruments)
            {
                var linkedCurves = instrument[Params.LinkedCurve.ToString()].ToString();
                if (!string.IsNullOrEmpty(linkedCurves))
                {
                    foreach (var linkedCurve in linkedCurves.Split(','))
                    {
                        if (linkedCurve.Equals(shock.Key) && (curveNames == null || curveNames.Contains(linkedCurve)))
                        {
                            Value(string.Format("{0}|{1}", shock.Key, Shock(instrument, linkedCurve, shock.Value)), false, trades);
                            Shock(instrument, linkedCurve, 0);
                        }
                    }
                }
            }
        }

        private void Value(string key, bool isBase, IDictionary<string, IFinPlusFunction> trades)
        {
            try
            {
                 var res = trades == null ? ValueCache(key, isBase) : ValueTrades(key, isBase, trades);
            }
            catch (Exception e)
            {
                IsValid = log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        private bool ValueCache(string key, bool isBase)
        {
            try
            {
                string res;
                lock (_analytics.QL)
                    res = _analytics.QL.MarketValueTrades(_analytics.MarketName,  _analytics.CacheName, "NPV");

                foreach (var val in res.Split(';'))
                    AddValue(key, (object[,])u.StringToArray2d(val, ';', ','), 0, isBase);

                return true;
            }
            catch (Exception e)
            {
                return IsValid = log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        private bool ValueTrades(string key, bool isBase, IDictionary<string, IFinPlusFunction> trades)
        {
            try
            {
                foreach (var trade in trades.Values)
                    ValueTrade(key, isBase, trade.Id.ToString());

                return true;
            }
            catch (Exception e)
            {
                return IsValid = log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        private void ValueTrade(string key, bool isBase, string id)
        {
            try
            {
                string res;
                lock (_analytics.QL)
                    res = _analytics.QL.MarketValueTrade(_analytics.MarketName, _analytics.CacheName, id, "NPV");

                AddValue(key, (object[,])u.StringToArray2d(res, ';', ','), 1, isBase);
            }
            catch (Exception e)
            {
                IsValid = log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        private void AddValue(string key, object[,] val, int startRow, bool isBase)
        {
            try
            {
                var res = _accum.TryAdd(key, val, startRow, isBase);
                Report(val, !res);
            }
            catch 
            {
                Report(val, true);
            }
        }

        private void Report(object[,] val, bool hasFailed)
        {
            try
            {
                if (_reportConn == null) return;

                var upper = val.GetUpperBound(0);
                var id = int.Parse(val[upper, 0].ToString());

                if (hasFailed && !_reportConn.Model.ContainsKey(id))
                    _reportConn.Model.Update(new Function(Params.FailedTrade.ToString(), val[upper, 1].ToString(), new Function(), id), ChangeType.New);
                else if (!hasFailed && _reportConn.Model.ContainsKey(id))
                    _reportConn.Model.Update(_reportConn.Model[id], ChangeType.Removed);
            }
            catch (Exception e)
            {
                IsValid = log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        private string Shock(IFinPlusFunction instrument, string curveName, int value)
        {
            var instrumentName = instrument[Params.Instrument.ToString()].ToString();
           
            var s = new Function(Params.Shock.ToString());
            s.Add(Params.CurveName.ToString(), curveName);
            s.Add(Params.Instrument.ToString(), instrumentName);
            s.Add(Params.Shock.ToString(), value);

            instrument.Get(Params.Shock.ToString(), string.Empty).Set(s);

            return instrumentName;
        }

        //cmds 
        private new enum Commands { TradeUpdate, MarketUpdate }

        //events
        private void Trade_Updated(object sender, ItemEventArgs a)
        {
            var func = new Function(Commands.TradeUpdate.ToString(), Id, User, -1);
            func.Add(Commands.TradeUpdate.ToString(), (IFinPlusFunction)sender, User);
            CommandRouter(func);
        }
        private void Market_Updated(object sender, ItemEventArgs a) 
        {
            //if (a.Item.Value.Equals(Params.MarketUpdate.ToString()))
                CommandRouter(new Function(Commands.MarketUpdate.ToString(), Id, User, -1));
        }
    }
}
